本篇會用前兩篇提到的 Class 與 Interface 玩轉設計模式中的「策略模式」,希望能透過實際的運用讓大家更了解兩者的運用觀念,只要能夠好好運用,就會發現許多設計模式都很有趣,非常值得一學。
在開始前簡單介紹一下什麼是策略模式,
上方提到既相同又不太一樣的行爲似乎有點矛盾,但是仔細想想,以遊戲中的「攻擊」這個動作好了,劍士、弓箭手、魔法師都會攻擊,但是它們攻擊的方法都不一樣對吧?
於是相同的行為就是「攻擊」,於是我們將「攻擊」給抽出來,設計成 Interface ,並實作出幾種 Class ,代表各種「達成行為的方式」,以下程式碼會在目錄中建立 Strategy_Pattern/index.ts, 並實作於其中:
interface IAttackBehavior {
attack: () => string;
}
class Cut implements IAttackBehavior {
public attack(): string {
return '砍劈攻擊';
}
}
class Magic implements IAttackBehavior {
public attack(): string {
return '魔法攻擊';
}
}
class Shooting implements IAttackBehavior {
public attack(): string {
return '射擊攻擊';
}
}
現在我們有了幾種攻擊的行為,接著創建一個有攻擊行為的人:
class Person {
public name: string;
constructor(name: string) {
this.name = name;
}
public attack(): void {
}
}
該怎麼做才有辦法讓 Person
能夠不寫死,並隨情況改變攻擊方式呢?
答案很簡單,只需要應用 Interface 就行了,我們可以在一開始創建新角色的時候,將該角色的攻擊模式和 name
一起傳入 constructor
中,並將攻擊模式寫入該角色的 Property,例如:
class Person {
public name: string;
public attackBehavior: IAttackBehavior;
constructor(name: string, attackBehavior: IAttackBehavior) {
this.name = name;
this.attackBehavior = attackBehavior;
}
public attack(): void {
}
}
接下來要怎麼做大家應該很明白了!既然攻擊模式在 constructor
中已經寫入型別為 Interface IAttackBehavior
的 this.attackBehavior
中了,那便直接對 Interface 做操作,也就是用 this.attackBehavior
執行 attack
的 Method 就好:
class Person {
/* 其餘省略 */
public attack(): void {
// 從 this.attackBehavior 中執行 attack
console.log(`${this.name}發動:${this.attackBehavior.attack()}`);
}
}
這麼一來,在一開始創建 Person
的時候,就能先為角色指定一種攻擊模式:
const character = new Person('神 Q 超人', new Cut());
character.attack(); // 神 Q 超人發動:砍劈攻擊
但是這麼做又有什麼彈性可言?還不是只有一種攻擊模式嗎?不如直接把執行寫在裡面,何必多寫那麼多行為的 Interface 及 Class?別急,厲害的來了,記得 Person
裡的 attackBehavior
的型別是 IAttackBehavior
嗎?也就是說所有的攻擊模式都可以放到這個 Property 中,因為只要是攻擊模式都會擁有 IAttackBehavior
接口,至於怎麼替換?就替 Person
增加一個替換 attackBehavior
的 Property Method 吧:
class Person {
/* 其餘省略 */
public changeAttackBehavior(attackBehavior: IAttackBehavior) {
this.attackBehavior = attackBehavior;
}
}
如此一來,被建立出來的角色就能以 changeAttackBehavior
來變更自身的攻擊模式了:
const character = new Person('神 Q 超人', new Cut());
character.attack(); // 神 Q 超人發動:砍劈攻擊
character.changeAttackBehavior(new Magic());
character.attack(); // 神 Q 超人發動:魔法攻擊
character.changeAttackBehavior(new Shooting());
character.attack(); // 神 Q 超人發動:射擊攻擊
這時候如果要再多個攻擊模式,只需要再添加一個實作 IAttackBehavior
的 Class 就好,其餘的程式碼都不需要改變,不只完美的履行 開放封閉原則,而且 Person
只對 Interface 做操作,根本就不在乎是誰實作了它,這部分也把「攻擊」的邏輯給封裝起來了。
本文的範例程式碼會提供在 GitHub 上,歡迎各位參考:)
本篇文章利用了 Strategy Pattern 策略模式來解說 Class 與 Interface,希望能讓人感受到它們的魅力或是對設計有不同的想法,如果看完有任何感想都歡迎告訴我!對設計模式有興趣的話,版上也有許多關於設計模式的文章,雖然以前只用 JavaScript 時就像圈外人,但現在就能用 TypeScript 玩轉一波了!
如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!
在這裡分享看完連續兩篇文章,我體會使用Interface的優點如下:
假設Person的攻擊動作不是使用Interface輸入而是使用class輸入
問題 : 當接收不同的攻擊class時就需要撰寫不同的Person來承接不同的class輸入,明明Person裡面的動作都一樣,但為了一點點的不同就需要多寫許多Person...
解決 : 所以使用Interface增加Person接收資料的彈性,符合攻擊介面,有實作出來的class,可以讓Person後續的動作抓的到值輸出都可以接收!!
不知這樣理解對不對??